package hollycrm.com.libuip.sip;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.WorkerThread;

import org.pjsip.pjsua2.AccountConfig;
import org.pjsip.pjsua2.AuthCredInfo;
import org.pjsip.pjsua2.AuthCredInfoVector;
import org.pjsip.pjsua2.BuddyConfig;
import org.pjsip.pjsua2.ContainerNode;
import org.pjsip.pjsua2.Endpoint;
import org.pjsip.pjsua2.EpConfig;
import org.pjsip.pjsua2.JsonDocument;
import org.pjsip.pjsua2.LogConfig;
import org.pjsip.pjsua2.OnRegStateParam;
import org.pjsip.pjsua2.StringVector;
import org.pjsip.pjsua2.TransportConfig;
import org.pjsip.pjsua2.UaConfig;
import org.pjsip.pjsua2.pj_log_decoration;
import org.pjsip.pjsua2.pjsip_status_code;
import org.pjsip.pjsua2.pjsip_transport_type_e;

import java.io.File;
import java.util.ArrayList;

import hollycrm.com.libuip.utils.AvailablePortFinder;

/**
 * Created by aber on 2017/1/19.
 */

public final class SipApp {
    /**
     *
     * all the call back invoke in pjsip thread
     * */
    public interface SipObserver {
        void notifyRegState(pjsip_status_code code, String reason, int expiration);
        void notifyIncomingCall(UIPCall call);
        void notifyCallState(UIPCall call);
        void notifyCallMediaState(UIPCall call);
        void notifyBuddyState(UIPBuddy buddy);
    }

    static {
        try{
            System.loadLibrary("openh264");
            // Ticket #1937: libyuv is now included as static lib
            //System.loadLibrary("yuv");
        } catch (UnsatisfiedLinkError e) {
            System.out.println("UnsatisfiedLinkError: " + e.getMessage());
            System.out.println("This could be safely ignored if you " +
                    "don't need video.");
        }
        System.loadLibrary("pjsua2");
        System.out.println("Library loaded");
    }

    public static Endpoint ep = new Endpoint();
    public static SipObserver observer;
    public ArrayList<UIPAccount> accList = new ArrayList<>();

    private ArrayList<UIPAccountConfig> accCfgs = new ArrayList<>();
    private EpConfig epConfig = new EpConfig();
    private TransportConfig sipTpConfig = new TransportConfig();
    private String appDir;

    /* Maintain reference to log writer to avoid premature cleanup by GC */
    private UIPLogWriter logWriter;

    private final String configName = "pjsua2.json";
    private int SIP_PORT  = 6000;
    private final int LOG_LEVEL = 4;

    public static Handler handler;


    public void init(SipObserver obs, String app_dir) {
        init(obs, app_dir, false);
    }

    public void init(SipObserver obs, String app_dir,
                     boolean own_worker_thread) {
        observer = obs;
        appDir = app_dir;
        /* Create endpoint */
        try {
            ep.libCreate();
        } catch (Exception e) {
            return;
        }
        /* Load config */
        String configPath = appDir + "/" + configName;
        File f = new File(configPath);
        if (f.exists()) {
            loadConfig(configPath);
        } else {
            /* Set 'default' values */
            if (!AvailablePortFinder.available(SIP_PORT)) {
                SIP_PORT = AvailablePortFinder.getNextAvailable();
            }
            sipTpConfig.setPort(SIP_PORT);
        }
        /* Override log level setting */
        epConfig.getLogConfig().setLevel(LOG_LEVEL);
        epConfig.getLogConfig().setConsoleLevel(LOG_LEVEL);
        /* Set log config. */
        LogConfig log_cfg = epConfig.getLogConfig();
        logWriter = new UIPLogWriter();
        log_cfg.setWriter(logWriter);
        log_cfg.setDecor(log_cfg.getDecor() &
                ~(pj_log_decoration.PJ_LOG_HAS_CR.swigValue() |
                        pj_log_decoration.PJ_LOG_HAS_NEWLINE.swigValue()));
        /* Set ua config. */
        UaConfig ua_cfg = epConfig.getUaConfig();
        ua_cfg.setUserAgent("Pjsua2 Android " + ep.libVersion().getFull());
        StringVector stun_servers = new StringVector();
        stun_servers.add("stun.pjsip.org");
        ua_cfg.setStunServer(stun_servers);
        if (own_worker_thread) {
            ua_cfg.setThreadCnt(0);
            ua_cfg.setMainThreadOnly(true);
        }

        /* Init endpoint */
        try {
            ep.libInit(epConfig);
        } catch (Exception e) {
            return;
        }

        /* Create transports. */
        try {
            ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP,
                    sipTpConfig);
        } catch (Exception e) {
            System.out.println(e);
        }

        try {
            ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP,
                    sipTpConfig);
        } catch (Exception e) {
            System.out.println(e);
        }

        /* Create accounts. */
        for (int i = 0; i < accCfgs.size(); i++) {
            UIPAccountConfig my_cfg = accCfgs.get(i);
            /* Customize account config */

            my_cfg.accCfg.getNatConfig().setIceEnabled(true);
            my_cfg.accCfg.getVideoConfig().setAutoTransmitOutgoing(true);
            my_cfg.accCfg.getVideoConfig().setAutoShowIncoming(true);

            UIPAccount acc = addAcc(my_cfg.accCfg);
            if (acc == null)
                continue;

            /* Add Buddies */
            for (int j = 0; j < my_cfg.buddyCfgs.size(); j++) {
                BuddyConfig bud_cfg = my_cfg.buddyCfgs.get(j);
                acc.addBuddy(bud_cfg);
            }
        }

        /* Start. */
        try {
            ep.libStart();
            //ep.libHandleEvents()
        } catch (Exception e) {
            return;
        }
    }

    public UIPAccount addAcc(AccountConfig cfg) {
        UIPAccount acc = new UIPAccount(cfg);
        try {
            acc.create(cfg);
        } catch (Exception e) {
            acc = null;
            return null;
        }
        accList.add(acc);
        return acc;
    }

    public void delAcc(UIPAccount acc) {
        accList.remove(acc);
    }

    private void loadConfig(String filename) {
        JsonDocument json = new JsonDocument();

        try {
            /* Load file */
            json.loadFile(filename);
            ContainerNode root = json.getRootContainer();

            /* Read endpoint config */
            epConfig.readObject(root);

            /* Read transport config */
            ContainerNode tp_node = root.readContainer("SipTransport");
            sipTpConfig.readObject(tp_node);

            /* Read account configs */
            accCfgs.clear();
            ContainerNode accs_node = root.readArray("accounts");
            while (accs_node.hasUnread()) {
                UIPAccountConfig acc_cfg = new UIPAccountConfig();
                acc_cfg.readObject(accs_node);
                accCfgs.add(acc_cfg);
            }
        } catch (Exception e) {
            System.out.println(e);
        }

        /* Force delete json now, as I found that Java somehow destroys it
        * after lib has been destroyed and from non-registered thread.
         */
        json.delete();
    }

    private void buildAccConfigs() {
        /* Sync accCfgs from accList */
        accCfgs.clear();
        for (int i = 0; i < accList.size(); i++) {
            UIPAccount acc = accList.get(i);
            UIPAccountConfig my_acc_cfg = new UIPAccountConfig();
            my_acc_cfg.accCfg = acc.cfg;

            my_acc_cfg.buddyCfgs.clear();
            for (int j = 0; j < acc.buddyList.size(); j++) {
                UIPBuddy bud = acc.buddyList.get(j);
                my_acc_cfg.buddyCfgs.add(bud.cfg);
            }

            accCfgs.add(my_acc_cfg);
        }
    }

    private void saveConfig(String filename) {
        JsonDocument json = new JsonDocument();

        try {
            /* Write endpoint config */
            json.writeObject(epConfig);

            /* Write transport config */
            ContainerNode tp_node = json.writeNewContainer("SipTransport");
            sipTpConfig.writeObject(tp_node);

            /* Write account configs */
            buildAccConfigs();
            ContainerNode accs_node = json.writeNewArray("accounts");
            for (int i = 0; i < accCfgs.size(); i++) {
                accCfgs.get(i).writeObject(accs_node);
            }

            /* Save file */
            json.saveFile(filename);
        } catch (Exception e) {}

        /* Force delete json now, as I found that Java somehow destroys it
        * after lib has been destroyed and from non-registered thread.
        */
        json.delete();
    }

    public void deinit() {
        String configPath = appDir + "/" + configName;
        saveConfig(configPath);

        /* Try force GC to avoid late destroy of PJ objects as they should be
        * deleted before lib is destroyed.
        */
        Runtime.getRuntime().gc();

        /* Shutdown pjsua. Note that Endpoint destructor will also invoke
        * libDestroy(), so this will be a test of double libDestroy().
        */
        try {
            ep.libDestroy();
        } catch (Exception e) {}

        /* Force delete Endpoint here, to avoid deletion from a non-
        * registered thread (by GC?).
        */
        ep.delete();
        ep = null;
    }


    public static AccountConfig newAccountConfig(
            String acc_id,
            String registrar,
            String proxy,
            String username,
            String password) {

        AccountConfig newConfig = new AccountConfig();
        newConfig.setIdUri(acc_id);
        newConfig.getRegConfig().setRegistrarUri(registrar);

        AuthCredInfoVector creds = newConfig.getSipConfig().
                getAuthCreds();
        creds.clear();
        if (username.length() != 0) {
            creds.add(new AuthCredInfo("Digest", "*", username, 0,
                    password));
        }
        StringVector proxies = newConfig.getSipConfig().getProxies();
        proxies.clear();
        if (proxy.length() != 0) {
            proxies.add(proxy);
        }
        /* Enable ICE */
        newConfig.getNatConfig().setIceEnabled(true);
        newConfig.getNatConfig().setIceAlwaysUpdate(true);
        newConfig.getVideoConfig().setAutoTransmitOutgoing(true);
        newConfig.getVideoConfig().setAutoShowIncoming(true);
        return newConfig;
    }
}
